//
//	Implementation of the Hex-View window
//
#include <windows.h>
#include "HexPrivate.h"

#include "HexView.h"

char szHexViewClassName[] = "MyHexView";

//
//	Main class constructor
//
HexView::HexView(HWND h) : hwnd(h)
{
	TEXTMETRIC tm;
	HDC hdc;
	HANDLE hold;
	
	nFileLength = 0;

	nBytesPerLine = 16;
	nVScrollPos = 0; nNumLines = 0; xChar = 10; yChar = 13; 
	nHScrollPos = 0; 
	nMaxDisplayWidth = 
		8 +				//adress
		2 +				//padding
		nBytesPerLine * 3 +	//hex column
		1 +				//padding
		nBytesPerLine;	//ascii column
	
	hdc = GetDC(0);
	
	hFont = CreateFont(12, 0, 0, 0, 0, 0, 0, 0, ANSI_CHARSET, 0, 0, DEFAULT_QUALITY, FIXED_PITCH, "Courier");
	hold = SelectObject(hdc, hFont);
	
	ZeroMemory(&tm, sizeof(tm));
	GetObject(hFont, sizeof(tm), &tm);
	
	SelectObject(hdc, hold);
	ReleaseDC(0, hdc);
	
	xChar = tm.tmAveCharWidth;
	yChar = tm.tmHeight + 1;
	if(xChar == 0) xChar = 8;
}

HexView::~HexView()
{

}

BOOL HexView::CloseHexFile()
{
	seqDoc.close();
	nFileLength = 0;

	return TRUE;
}

//
//	
//
BOOL HexView::OpenHexFile(char *filename)
{
	//get the sequence document to open the file for us
	if(seqDoc.init(filename) == FALSE)
		return FALSE;

	nFileLength = seqDoc.size();
	nNumLines   = nFileLength / nBytesPerLine;
	
	if(nFileLength % nBytesPerLine)
		nNumLines++;

	SetupScrollbars();
	InvalidateRect(hwnd,0,TRUE);

	return TRUE;
}

void HexView::SetupScrollbars()
{
	SCROLLINFO sInfo;
	
	//
	//	Vertical scrollbar
	//
	nPageMaxLines = min(nNumLines, (size_w)nHeight / yChar);
	nVScrollMax = max(0, nNumLines-1);
    nVScrollPos = min(nVScrollPos, nVScrollMax-nPageMaxLines+1);
	
	sInfo.cbSize = sizeof SCROLLINFO;
	sInfo.fMask = SIF_PAGE | SIF_POS | SIF_RANGE | SIF_DISABLENOSCROLL;
	sInfo.nMin	= 0;
	sInfo.nMax	= nVScrollMax;
	sInfo.nPos	= nVScrollPos;
	sInfo.nPage	= min(nPageMaxLines, nVScrollMax+1);
	SetScrollInfo (hwnd, SB_VERT, &sInfo, TRUE);

	//
	//	Horizontal scrollbar (not implemted by WM_HSCROLL yet...)
	//
	nPageMaxCols = min(nMaxDisplayWidth+1, nWidth / xChar);	
    nHScrollMax = nMaxDisplayWidth;
    nHScrollPos = min(nHScrollPos, nHScrollMax-nPageMaxCols+1);
	
	sInfo.cbSize = sizeof SCROLLINFO;
	sInfo.fMask = SIF_POS | SIF_PAGE | SIF_RANGE;
	sInfo.nMin	= 0;
	sInfo.nMax	= nHScrollMax;
	sInfo.nPos	= nHScrollPos;
	sInfo.nPage	= min(nPageMaxCols, nHScrollMax+1);

	SetScrollInfo (hwnd, SB_HORZ, &sInfo, TRUE);

}	

//
//	WM_VSCROLL
//
LRESULT HexView::OnVScroll(WPARAM wParam, LPARAM lParam)
{
	// React to the various vertical scroll related actions.
	// CAUTION:
	// All sizes are in unsigned values, so be carefull
	// when testing for < 0 etc

	SCROLLINFO info;
	size_w nVScrollInc;
	size_w  nOldVScrollPos = nVScrollPos;
		
	HideCaret(hwnd);

	switch (LOWORD(wParam))
	{
	case SB_TOP:
		nVScrollPos = 0;
		break;

	case SB_BOTTOM:
		nVScrollPos = nVScrollMax - nPageMaxLines + 1;
		break;

	case SB_LINEUP:
		if(nVScrollPos > 0) --nVScrollPos;
		break;

	case SB_LINEDOWN:
		nVScrollPos++;
		break;

	case SB_PAGEUP:
		nVScrollPos -= max(1, nPageMaxLines);
		if(nVScrollPos > nOldVScrollPos) nVScrollPos = 0;
		break;

	case SB_PAGEDOWN:
		nVScrollPos += max(1, nPageMaxLines);
		break;

	case SB_THUMBPOSITION:
		info.cbSize = sizeof(SCROLLINFO);
		info.fMask = SIF_TRACKPOS;
		GetScrollInfo(hwnd, SB_VERT, &info);
		nVScrollPos = info.nTrackPos;
		break;

	case SB_THUMBTRACK:
		info.cbSize = sizeof(SCROLLINFO);
		info.fMask = SIF_TRACKPOS;
		GetScrollInfo(hwnd, SB_VERT, &info);
		nVScrollPos = info.nTrackPos;
		break;

	default:
		nVScrollInc = 0;
	}

	//keep scroll position in range
	if(nVScrollPos > nVScrollMax - nPageMaxLines+1)
		nVScrollPos = nVScrollMax - nPageMaxLines+1;

	nVScrollInc = nVScrollPos - nOldVScrollPos;
		
	if (nVScrollInc)
	{
		RECT r, clip; 

		r.left = 0; r.right = nWidth;
		r.top = 0; r.bottom = nPageMaxLines*yChar-yChar;

		clip.top = 0;
		clip.left = 0;
		clip.right = nWidth;
		clip.bottom = nHeight;
		
		//we may be scrolling so that the "half" line at the bottom
		//appears with no selection, so need to invalidate where
		//that portion is going to get scrolled TO
		if(nVScrollInc > 0)
		{
			RECT arse;
			SetRect(&arse, 0, 0, nWidth, yChar);
			InvalidateRect(hwnd, &arse, FALSE);
		}
		else if(nVScrollInc < 0)
		{
			RECT arse;
			SetRect(&arse, 0, nPageMaxLines*yChar, nWidth, nHeight);
			InvalidateRect(hwnd, &arse, FALSE);
		}

		ScrollWindowEx(hwnd, 0, -yChar * nVScrollInc, NULL, &clip, NULL, &r, SW_INVALIDATE);
		
		//finally setup the actual scrollbar!
		info.cbSize = sizeof(SCROLLINFO);
		info.fMask = SIF_POS;
		info.nPos = nVScrollPos;
		SetScrollInfo(hwnd, SB_VERT, &info, TRUE);
				
		UpdateWindow(hwnd);
	}
	
	return 0;
}

//
//	Create 1 line of a hex-dump, given a buffer of BYTES
//
void HexView::RenderHexData(char *buf, BYTE *data, size_w offset, size_w length)
{
	char *bptr = buf;
	size_t i;

	//draw the hex address
	bptr += wsprintf(bptr, "%08X  ", offset);

	//draw hex data
	for(i = 0; i < length; i++)
	{
		bptr += wsprintf(bptr, "%02X ", data[i]);
	}

	//draw some blanks if this isn't a full line
	for(; i < nBytesPerLine; i++)
	{
		bptr += wsprintf(bptr, "   ");
	}

	//draw a gap between the hex and ascii
	bptr += wsprintf(bptr, " ");

	//draw the ascii
	for(i = 0; i < length; i++)
	{
		BYTE c = data[i];
		if(c < 32 || c > 128) c = '.';
		bptr += wsprintf(bptr, "%c", c);
	}

	//draw some blanks if this isn't a full line
	for(; i < nBytesPerLine; i++)
	{
		bptr += wsprintf(bptr, " ");
	}
}

//
//	Draw 1 line to the display
//
void HexView::DrawLine(HDC hdc, DWORD nLineNo)
{
	char szLine[100];
	BYTE buf[100];
	size_w length = nBytesPerLine;

	if(nFileLength - nLineNo * nBytesPerLine < length)
		length = nFileLength - nLineNo * nBytesPerLine;

	//get data from our file handler object
	seqDoc.render(buf, nLineNo * nBytesPerLine, length);
	
	//convert the data into a one-line hex-dump
	RenderHexData(szLine, buf, nLineNo, length);

	//draw this line to the screen
	TextOut(hdc, -(int)(nHScrollPos * xChar), (nLineNo - nVScrollPos) * yChar , szLine, lstrlen(szLine));
}

//
//	WM_PAINT
//
LRESULT HexView::OnPaint(WPARAM wParam, LPARAM lParam)
{
	HDC hdc;
	PAINTSTRUCT ps;
	RECT rc;
	size_w i;
	size_w iPaintBeg, iPaintEnd;
	
	HANDLE hold;
	
	hdc = BeginPaint(hwnd, &ps);
	hold = SelectObject(hdc, hFont);
	
	iPaintBeg = max(0, nVScrollPos + ps.rcPaint.top / yChar );			//only update the lines that 
	iPaintEnd = min(nNumLines, nVScrollPos + ps.rcPaint.bottom / yChar);		//need updating!!!!!!!!!!!!!
	
	if(ps.rcPaint.bottom % yChar) iPaintEnd++;
	if(iPaintEnd > nNumLines) iPaintEnd--;
	
	//
	//	Only paint what needs to be!
	//
	for(i = iPaintBeg; i < iPaintEnd; i++)
	{
		DrawLine(hdc, i);
		
		//fill any extra space to the right
		if(nWidth > nMaxDisplayWidth * xChar)
		{
			SetRect(&rc, nMaxDisplayWidth * xChar, (i-nVScrollPos) * yChar, nWidth, (i-nVScrollPos+1) * yChar);
			ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, "", 0, 0);
		}
	}
	
	//fill any extra space below the hex dump
	if(nVScrollPos == nVScrollMax - nPageMaxLines + 1)
	{
		SetRect(&rc, 0, nPageMaxLines * yChar, nWidth, nHeight);
		ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, "", 0, 0);
	}
	
	//if need to paint below..
	if(nNumLines == 0 || nNumLines < nPageMaxLines)
	{
		SetRect(&rc, 0, nNumLines * yChar, nWidth, nHeight);
		ExtTextOut(hdc, 0, 0, ETO_OPAQUE, &rc, "", 0, 0);
	}
	
	
	SelectObject(hdc, hold);
	EndPaint(hwnd, &ps);
	
	return 0;
}

//
//	Handler for WM_SIZE
//
LRESULT HexView::OnSize(WPARAM wParam, LPARAM lParam)
{
	size_w oldVScrollPos = nVScrollPos;

	nWidth = LOWORD(lParam);
	nHeight = HIWORD(lParam);

	SetupScrollbars();


	//redraw the window if the user re-sizes, and we are scrolled to the
	//end of the data - in this case, we need to "drag" the data down
	//so that we always see the end of the document
	if(nVScrollMax < nPageMaxLines && oldVScrollPos != nVScrollPos ||
		nVScrollPos != 0 && nVScrollPos == nVScrollMax - nPageMaxLines+1 && oldVScrollPos != nVScrollPos)
	{
		InvalidateRect(hwnd, NULL, FALSE);
		UpdateWindow(hwnd);
	}

	return 0;
}

//
//	Window procedure for the HexView
//
LRESULT CALLBACK HexWndProc(HWND hwnd, UINT msg, WPARAM wParam, LPARAM lParam)
{
	HexView *hvp = (HexView *)GetWindowLong(hwnd, 0);

	switch(msg)
	{
	case WM_NCCREATE:
		hvp = new HexView(hwnd);
		SetWindowLong(hwnd, 0, (LONG)hvp);
		return 1;

	case WM_NCDESTROY:
		delete hvp;
		return 0;

	case WM_PAINT:
		return hvp->OnPaint(wParam, lParam);

	case WM_SIZE:
		return hvp->OnSize(wParam, lParam);

	case WM_VSCROLL:
		return hvp->OnVScroll(wParam, lParam);


	}
	return DefWindowProc(hwnd, msg, wParam, lParam);
}

//
//	Register the HexView WINDOW class
//  For some reason, "HexView" is already registered in Win2000,
//	so we need to use MyHexView instead!!
//
void InitHexView()
{
	WNDCLASSEX	wndclass;

	//Window class for the main application parent window
	wndclass.cbSize			= sizeof(wndclass);
	wndclass.style			= 0;
	wndclass.lpfnWndProc	= HexWndProc;
	wndclass.cbClsExtra		= 0;
	wndclass.cbWndExtra		= sizeof(HexView *);
	wndclass.hInstance		= GetModuleHandle(0);
	wndclass.hIcon			= 0;
	wndclass.hCursor		= LoadCursor (NULL, IDC_IBEAM);
	wndclass.hbrBackground	= (HBRUSH)0;		//NO FLICKERING FOR US!!
	wndclass.lpszMenuName	= 0;
	wndclass.lpszClassName	= szHexViewClassName;	
	wndclass.hIconSm		= 0;

	RegisterClassEx(&wndclass);
}

//
//	Create a hexview control!
//
HWND CreateHexView(HWND hwndParent)
{
	return CreateWindowEx(WS_EX_CLIENTEDGE, 
		szHexViewClassName, "", 
		WS_VSCROLL |WS_HSCROLL | WS_CHILD | WS_VISIBLE,
		0, 0, 0, 0, 
		hwndParent, 
		0, 
		GetModuleHandle(0), 
		0);
}

